package business_logic.database;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import business_logic.GameElement;
import business_logic.view.GameView;

import model.collisions.Collisionable;
import model.elements.attributes.Coordinate2D;
import model.elements.behaviours.Positionable;
import model.map.PositionablesContainer;


public class GameDatabase implements PositionablesContainer
{
    
    private Set<GameDatabaseEntry> m_databaseEntries;

    public GameDatabase()
    {
        m_databaseEntries  = new HashSet<GameDatabaseEntry>();
    }

    public boolean addElement( GameElement gameElement )
    {
        synchronized (m_databaseEntries)
        {
            return m_databaseEntries.add( new GameDatabaseEntry(gameElement) );
        }
    }

    public void removeElement( GameElement gameElement )
    {
        synchronized (m_databaseEntries)
        {
            Set<GameDatabaseEntry> entriesToRemove = new HashSet<GameDatabaseEntry>();
            
            for ( GameDatabaseEntry databaseEntry : m_databaseEntries )
            {
                if( databaseEntry.getElement().equals(gameElement) )
                    entriesToRemove.add( databaseEntry );
            }
            
            for ( GameDatabaseEntry entryToRemove : entriesToRemove )
                m_databaseEntries.remove( entryToRemove );
        }
    }
    
    public Collection<GameElement> getUselessElements()
    {
        synchronized (m_databaseEntries)
        {
            Collection<GameElement> result = new HashSet<GameElement>();
            
            for ( GameDatabaseEntry databaseEntry : m_databaseEntries )
            {
                if( !databaseEntry.getElement().isAlive() )
                {
                    Collection<GameView> views = databaseEntry.getViews();
                    
                    if ( views.size() > 0 )
                    {
                        boolean allViewsDone = true;
                        
                        for ( GameView view : views )
                            if( ! view.isDone() )
                                allViewsDone = false;
                        
                        if ( allViewsDone )
                            result.add( databaseEntry.getElement() );
                    }
                }
            }
            
            return result;
        }
    }
    
    // TODO: Tirar una excepcion del tipo correcto si el elemento al que le estoy agregando la vista no está en la DB (o un error)
    public void addViewForElement( GameView view , GameElement gameElement) throws Exception
    {
        GameDatabaseEntry databaseEntry = this.getEntryByElement(gameElement);
        if( databaseEntry == null )
            throw new Exception();
        else
            databaseEntry.addView( view );
    }

    public Collection<GameElement> getElementsWithoutView()
    {
        synchronized (m_databaseEntries)
        {
            Collection<GameElement> result = new HashSet<GameElement>();
        
            for ( GameDatabaseEntry databaseEntry : m_databaseEntries )
            {
                if( databaseEntry.getViews().isEmpty() )
                    result.add( databaseEntry.getElement() );
            }
            
        
            return result;
        }
    }

    private GameDatabaseEntry getEntryByElement( GameElement element )
    {
        synchronized (m_databaseEntries)
        {
            GameDatabaseEntry result = null;
        
            for ( GameDatabaseEntry databaseEntry : m_databaseEntries )
            {
                if( databaseEntry.getElement().equals(element) )
                    result = databaseEntry;
            }
        
            return result;
        }
    }

    // OJO!!!!! ACÁ estoy haciendo un ACTO DE FE. O sea, supongo que solamente se van a añadir elementos de la capa de negocios, y no otros Elements2D. (cualquier cosa lo revisamos y hacemos un adapter o parser o algo asi)
    // TODO: Podríamos agregar alguna exception propia, por ahora solo va a tirar ClassCastException
    // Cual es la diferencia entre un Element2D y un BombitaRodriguezGameElement ? -> Básicamente el 2do extiende del 1ero y agrega e implementa la interfáz "hasViewIdentifier".
    @Override
    public boolean addElement(Positionable element)
    {
        return this.addElement( ( GameElement ) element);
    }

    @Override
    public void removeElement(Positionable element)
    {
        this.removeElement( ( GameElement ) element);
    }

    @Override
    public void clear()
    {
        synchronized (m_databaseEntries)
        {
            m_databaseEntries.clear();
        }
    }

    @Override
    public Collection<Collisionable> getElementsIn(Coordinate2D coord)
    {
        
        synchronized (m_databaseEntries)
        {
            Collection<Collisionable> result = new HashSet<Collisionable>();
            
            for ( GameDatabaseEntry databaseEntry : m_databaseEntries )
            {
                if( databaseEntry.getElement().isAlive() )
                    if( databaseEntry.getElement().getPosition().equals(coord) )
                        result.add(databaseEntry.getElement());
            }
            
            return result;            
        }
    }
    
    public boolean checkIfElementsIn(Coordinate2D coord)
    {
        synchronized (m_databaseEntries)
        {
            for ( GameDatabaseEntry databaseEntry : m_databaseEntries )
                if( databaseEntry.getElement().isAlive() 
                        && databaseEntry.getElement().getPosition().equals(coord) )
                    return true;
            return false;
        }
    }


    @Override
    public int getElementCount()
    {
        synchronized (m_databaseEntries)
        {
            int count = 0;
        
            for( GameDatabaseEntry databaseEntry : m_databaseEntries )
                if( databaseEntry.getElement()!=null )
                    count++;

            return count;
        }
    }

    public Collection<GameView> getViewsFor(GameElement element)
    {
        GameDatabaseEntry entry = this.getEntryByElement(element);
        return entry.getViews();
    }
    
    public Collection <GameElement> getElements()
    {
        synchronized (m_databaseEntries)
        {
            Collection <GameElement> result = new HashSet<GameElement>();
            
            for ( GameDatabaseEntry entry : m_databaseEntries )
                result.add( entry.getElement() );
            
            return result;
        }
    }
    
    public Element save()
    {
        Element root = DocumentHelper.createElement( "Game_Snapshot" );
        
        for ( GameDatabaseEntry entry : m_databaseEntries )
        {
            GameElement gameElement = entry.getElement();
            if ( gameElement.isAlive() )
            {
                root.add( gameElement.save() );
            }
        }
        
        return root;
    }
}
